/*
 * Copyright 2000-2005 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.idea.accurev.application;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeList;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.accurev.AccuRevBundle;
import org.jetbrains.idea.accurev.operations.P4AddOperation;
import org.jetbrains.idea.accurev.operations.P4DeleteOperation;
import org.jetbrains.idea.accurev.operations.VcsOperation;
import org.jetbrains.idea.accurev.accurev.PerforceAbstractChange;
import org.jetbrains.idea.accurev.accurev.PerforceChange;
import org.jetbrains.idea.accurev.accurev.AccuRevRunner;
import org.jetbrains.idea.accurev.accurev.AccuRevSettings;
import org.jetbrains.idea.accurev.accurev.connections.P4Connection;

import java.util.*;

public class PerforceCheckinEnvironment implements CheckinEnvironment{
  private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.accurev.application.PerforceCheckinEnvironment");

  private final Project myProject;
  private AccuRevRunner myRunner;

  public boolean showCheckinDialogInAnyCase() {
    return true;
  }

  public PerforceCheckinEnvironment(Project project) {
    myProject = project;
    myRunner = AccuRevRunner.getInstance(project);
  }

  @Nullable
  public RefreshableOnComponent createAdditionalOptionsPanel(final CheckinProjectPanel panel) {
    return null;
  }

  public String getDefaultMessageFor(FilePath[] filesToCheckin) {
    return null;
  }

  public String prepareCheckinMessage(String text) {
    return text;
  }

  public String getHelpId() {
    return null;
  }

  public String getCheckinOperationName() {
    return AccuRevBundle.message("operation.name.submit");
  }

  public List<VcsException> commit(List<Change> incomingChanges, String preparedComment) {
    if (preparedComment == null) {
      preparedComment = AccuRevBundle.message("default.non.empty.comment");
    }

    ArrayList<VcsException> vcsExceptions = new ArrayList<VcsException>();
    try {
      final AccuRevSettings settings = AccuRevSettings.getSettings(myProject);

      List<PerforceChange> changes = null;

      final ChangeList list = ChangesUtil.getChangeListIfOnlyOne(myProject, incomingChanges.toArray(new Change[incomingChanges.size()]));
      if (list != null) {
        final List<P4Connection> allConnections = settings.getAllConnections();
        if (allConnections.size() == 1) {
          final P4Connection connection = allConnections.get(0);
          Long changeListNumber = AccuRevChangeListSynchronizer.getInstance(myProject).getChangeListNumber(connection, list);
          if (changeListNumber != null) {
            changes = myRunner.getChanges(connection, changeListNumber.longValue());
          }
        }
      }
      if (changes == null) {
        changes = myRunner.getAllChanges();
      }

      List<PerforceChange> actualChanges = collectChanges(changes, ChangesUtil.getPaths(incomingChanges));
      if (!actualChanges.isEmpty()){
        try {
          for (SubmitJob job : createJobs(actualChanges)) {
            job.submit(preparedComment);
          }
        }
        catch (VcsException e) {
          vcsExceptions.add(e);
        }

      } else {
        vcsExceptions.add(new VcsException(AccuRevBundle.message("exception.text.nothing.found.to.submit")));
      }

      LOG.info("updating opened files after commit");
      AccuRevChangeListSynchronizer.getInstance(myProject).queueUpdateOpenedFiles();
    }
    catch (VcsException e) {
      vcsExceptions.add(e);
    } finally{
      AccuRevManager.getInstance(myProject).clearCache();
    }
    return vcsExceptions;
  }

  private Collection<SubmitJob> createJobs(List<PerforceChange> changes) {
    Map<P4Connection, SubmitJob> factoredJobs = new HashMap<P4Connection, SubmitJob>();
    for (PerforceChange change : changes) {
      P4Connection connection = change.getConnection();
      SubmitJob job = factoredJobs.get(connection);
      if (job == null) {
        job = new SubmitJob(connection);
        factoredJobs.put(connection, job);
      }
      job.addChange(change);
    }

    return factoredJobs.values();
  }

  private class SubmitJob {
    private final P4Connection myConnection;
    private final List<PerforceChange> myChanges = new ArrayList<PerforceChange>();

    public SubmitJob(final P4Connection connection) {
      myConnection = connection;
    }

    public void addChange(PerforceChange change) {
      myChanges.add(change);
    }

    public void submit(String comment) throws VcsException {
      if (myChanges.size() == 0) return;
      final PerforceClient client = AccuRevManager.getInstance(myProject).getClient(myConnection);
      long changeListID = createSingleChangeListForConnection(client);
      myRunner.submitForConnection(myConnection, myChanges, changeListID, comment);
    }

    private long createSingleChangeListForConnection(final PerforceClient client) throws VcsException {
      ArrayList<Long> changelists = new ArrayList<Long>();
      HashMap<Long, List<PerforceChange>> changesByList = new HashMap<Long, List<PerforceChange>>();
      for (PerforceChange change : myChanges) {
        final long changeList = change.getChangeList();
        if (!changelists.contains(changeList)) {
          changelists.add(changeList);
        }
        List<PerforceChange> listChanges = changesByList.get(changeList);
        if (listChanges == null) {
          listChanges = new ArrayList<PerforceChange>();
          changesByList.put(changeList, listChanges);
        }
        listChanges.add(change);
      }

      if (changelists.size() == 0) return -1;
      if (changelists.size() == 1) {
        return changelists.get(0).longValue();
      }

      boolean[] full = new boolean[changelists.size()];
      int fullListsCount = 0;
      long targetListID = -1;
      for (int i = 0; i < full.length; i++) {
        long changeList = changelists.get(i).longValue();
        final List<PerforceChange> allListChanges = myRunner.getChanges(myConnection, changeList, client);
        full[i] = myChanges.containsAll(allListChanges);
        if (full[i]) {
          targetListID = changeList;
          fullListsCount++;
        }
      }

      if (fullListsCount != 1) {
        targetListID = -1;
      }

      for (int i = 0; i < changelists.size(); i++) {
        long victim = changelists.get(i).longValue();
        if (victim != targetListID) {
          myRunner.reopen(myConnection, changesByList.get(victim), targetListID);
          if (full[i]) {
            myRunner.deleteEmptyChangeList(myConnection, victim);
          }
        }
      }

      return targetListID;
    }
  }

  public List<VcsException> scheduleMissingFileForDeletion(final List<FilePath> files) {
    final VcsBackgroundTask<FilePath> task = new VcsBackgroundTask<FilePath>(myProject, "Removing Files",
                                                                             VcsConfiguration.getInstance(myProject).getAddRemoveOption(),
                                                                             files) {
      protected void process(final FilePath item) throws VcsException {
        new P4DeleteOperation(ChangeListManager.getInstance(myProject).getDefaultChangeList().getName(), item).executeOrLog(myProject);
      }
    };
    AccuRevVcs.getInstance(myProject).runTask(task);
    return Collections.emptyList();
  }

  public List<VcsException> scheduleUnversionedFilesForAddition(final List<VirtualFile> files) {
    String activeChangeList = ChangeListManager.getInstance(myProject).getDefaultChangeList().getName();
    final List<VcsOperation> ops = new ArrayList<VcsOperation>();
    for(VirtualFile file: files) {
      ops.add(new P4AddOperation(activeChangeList, file));
    }
    AccuRevVcs.getInstance(myProject).runOrQueue(ops, AccuRevBundle.message("progress.title.running.perforce.commands"),
                                                  VcsConfiguration.getInstance(myProject).getAddRemoveOption());
    return Collections.emptyList();
  }

  static <T extends PerforceAbstractChange> List<T> collectChanges(List<T> changes, Collection<FilePath> paths) {
    ArrayList<T> result = new ArrayList<T>();
    for (FilePath file : paths) {
      T change = findChange(changes, file);
      if (change != null) {
        result.add(change);
      }
    }
    return result;
  }

  @Nullable
  private static <T extends PerforceAbstractChange> T findChange(List<T> changes, FilePath file) {
    for (T change : changes) {
      if (change.getFile().equals(file.getIOFile())) return change;
    }
    return null;
  }
}
